/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace DaveSexton.DocProject.Wizards.Foundation
{
	public sealed partial class WizardForm : Form
	{
		#region Public Properties
		[Category("Appearance")]
		public string WizardName
		{
			get
			{
				return wizardName;
			}
			set
			{
				wizardName = value;

				UpdateTitle();
			}
		}

		[Browsable(false)]
		public SettingsDictionary<string, object> WizardData
		{
			get
			{
				return controller.Data;
			}
		}

		[Browsable(false)]
		public MessageBoxOptions MessageBoxOptions
		{
			get
			{
				if (RightToLeft == RightToLeft.Yes)
					return MessageBoxOptions.RightAlign | MessageBoxOptions.RtlReading;
				else
					return (MessageBoxOptions) 0;
			}
		}

		/// <remarks>
		/// Visual Studio seems to ignore exceptions that are thrown in a code path that originates from a UI event such as a button press while the wizard form is being hosted by an Add-In.
		/// Since the <see cref="WizardController.Finish"/> method is called when the user presses the <em>Finish</em> or <em>Cancel</em> buttons, suppression of exceptions along with raising 
		/// the <see cref="Error"/> event is necessary so that errors may be logged and handled appropriately.
		/// </remarks>
		[Category("Behavior"), Description("Specifies whether an exception thrown by the wizard controller's Finish method should be caught and the Error event raised."), DefaultValue(true)]
		public bool SuppressFinishExceptions
		{
			get
			{
				return suppressExceptions;
			}
			set
			{
				suppressExceptions = value;
			}
		}

		[Browsable(false)]
		public WizardController Controller
		{
			get
			{
				return controller;
			}
		}
		#endregion

		#region Private / Protected
		private readonly WizardController controller;
		private string wizardName;
		private bool evaluatedCanCancelWizard, suppressChangedUpdate, suppressExceptions = true, finishedWithError;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="WizardForm" /> class for designer support.
		/// </summary>
		private WizardForm()
		{
			InitializeComponent();
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="WizardForm" /> class.
		/// </summary>
		public WizardForm(string wizardName, WizardStep firstStep)
			: this()
		{
			controller = new WizardController(this, firstStep);
			controller.CurrentStepStateChanged += controller_CurrentStepStateChanged;

			if (components == null)
				components = new Container();

			components.Add(controller);

			this.wizardName = wizardName;
			Text = wizardName;

			controller.Start(true);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="WizardForm" /> class.
		/// </summary>
		public WizardForm(string wizardName, WizardStep firstStep, WizardStep lastStep)
			: this()
		{
			controller = new WizardController(this, firstStep, lastStep);
			controller.CurrentStepStateChanged += controller_CurrentStepStateChanged;

			if (components == null)
				components = new Container();

			components.Add(controller);

			this.wizardName = wizardName;
			Text = wizardName;

			controller.Start(true);
		}
		#endregion

		#region Methods
		private void UpdateAll()
		{
			SuspendLayout();

			// update the interface first so that the current step will create its control before
			// methods such as "CanMoveNext" are invoked
			UpdateInterface();
			UpdateNavigation();
			UpdateTitle();

			ResumeLayout(true);
		}

		private void UpdateNavigation()
		{
			SuspendLayout();

			if (controller.IsCurrentLastVisibleStep)
			{
				if (controller.IsCurrentFirstVisibleStep)
				{
					if (!okButton.Visible)
					{
						okButton.Visible = true;
						cancelButton.Visible = true;
						nextButton.Visible = false;
						backButton.Visible = false;
						finishButton.Visible = false;
					}

					AcceptButton = (controller.SuppressDefaultNavigation) ? null : okButton;

					okButton.Enabled = controller.CanFinishWizard();
					cancelButton.Enabled = controller.CanCancelWizard();
				}
				else
				{
					if (!finishButton.Visible)
					{
						nextButton.Visible = false;
						cancelButton.Visible = false;
						finishButton.Visible = true;
					}

					AcceptButton = (controller.SuppressDefaultNavigation) ? null : finishButton;

					finishButton.Enabled = controller.CanFinishWizard();
				}
			}
			else
			{
				if (!nextButton.Visible)
				{
					nextButton.Visible = true;
					cancelButton.Visible = true;
					finishButton.Visible = false;
				}

				AcceptButton = (controller.SuppressDefaultNavigation) ? null : nextButton;

				cancelButton.Enabled = controller.CanCancelWizard();
				nextButton.Enabled = controller.CanMoveNext(true);
			}

			backButton.Enabled = controller.CanMovePrevious(true);

			ResumeLayout(true);
		}

		private void UpdateInterface()
		{
			SuspendLayout();
			
			hostPanel.Controls.Clear();

			Control control = controller.CurrentStepUserInterface as Control;

			ISupportInitialize init = control as ISupportInitialize;

			if (init != null)
				init.BeginInit();

			if (control == null)
			{
				control = new UserControl();

				Label label = new Label();
				label.Dock = DockStyle.Fill;
				label.TextAlign = ContentAlignment.MiddleCenter;
				label.Text = Resources.Text.WizardStepDidNotSupplyControl;
				label.Parent = control;
			}

			control.Dock = DockStyle.Fill;
			control.Parent = hostPanel;

			if (init != null)
				init.EndInit();

			ResumeLayout(true);

			// Force focus on the control so that the AcceptButton is updated immediately if it was changed; otherwise, the enter
			// key will continue to press the last default button until the control is focused.
			control.Focus();
		}

		private void UpdateTitle()
		{
			Text = wizardName + " - " + controller.CurrentStep.Name;
		}
		#endregion

		#region Events
		private readonly object WizardCanceledEvent = new object();
		private readonly object ErrorEvent = new object();

		/// <summary>
		/// Event raised when an error occurs during a wizard operation.
		/// </summary>
		[Category("Behavior")]
		[Description("Event raised when an error occurs during a wizard operation.")]
		public event EventHandler<ExceptionEventArgs> Error
		{
			add
			{
				lock (ErrorEvent)
				{
					Events.AddHandler(ErrorEvent, value);
				}
			}
			remove
			{
				lock (ErrorEvent)
				{
					Events.RemoveHandler(ErrorEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="Error" /> event.
		/// </summary>
		/// <param name="e"><see cref="ExceptionEventArgs" /> object that provides the arguments for the event.</param>
		private void OnError(ExceptionEventArgs e)
		{
			EventHandler<ExceptionEventArgs> handler = null;

			lock (ErrorEvent)
			{
				handler = (EventHandler<ExceptionEventArgs>) Events[ErrorEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the user attempts to cancel the wizard.
		/// </summary>
		[Category("Action")]
		[Description("Event raised when the user attempts to cancel the wizard.")]
		public event CancelEventHandler WizardCanceled
		{
			add
			{
				lock (WizardCanceledEvent)
				{
					Events.AddHandler(WizardCanceledEvent, value);
				}
			}
			remove
			{
				lock (WizardCanceledEvent)
				{
					Events.RemoveHandler(WizardCanceledEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="WizardCanceled" /> event.
		/// </summary>
		/// <param name="e"><see cref="CancelEventArgs" /> object that provides the arguments for the event.</param>
		private void OnWizardCanceled(CancelEventArgs e)
		{
			CancelEventHandler handler = null;

			lock (WizardCanceledEvent)
			{
				handler = (CancelEventHandler) Events[WizardCanceledEvent];
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion

		#region Event Handlers
		protected override void OnLoad(EventArgs e)
		{
			UpdateAll();

			base.OnLoad(e);
		}

		protected override void OnFormClosing(FormClosingEventArgs e)
		{
			if (!finishedWithError && controller.Started && e.CloseReason == CloseReason.UserClosing)
			// the finishButton and okButton click event handlers are the only methods that finish the wizard;
			// if it's still running (controller.Started) then cancel it
			{
				if (!evaluatedCanCancelWizard && !controller.CanCancelWizard())
				{
					MessageBox.Show(Owner,
						Resources.Text.WizardCannotBeCanceled,
						Resources.Text.WizardCannotBeCanceledTitle,
						MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1, MessageBoxOptions);

					e.Cancel = true;
				}
				else
				{
					CancelEventArgs ce = new CancelEventArgs();

					OnWizardCanceled(ce);

					e.Cancel = ce.Cancel;
				}

				evaluatedCanCancelWizard = false;
			}

			base.OnFormClosing(e);
		}

		protected override void OnFormClosed(FormClosedEventArgs e)
		{
			if (!finishedWithError && controller.Started && e.CloseReason == CloseReason.UserClosing)
				controller.Finish(true);

			base.OnFormClosed(e);
		}

		private void controller_CurrentStepStateChanged(object sender, WizardStepStateChangedEventArgs e)
		{
			if (!suppressChangedUpdate)
			{
				UpdateNavigation();
				UpdateTitle();
			}
		}

		private void finishButton_Click(object sender, EventArgs e)
		{
			if (!controller.CanFinishWizard())
				UpdateNavigation();
			else
			{
				Hide();

				try
				{
					controller.Finish(false);
				}
				catch (Exception ex)
				{
					if (suppressExceptions)
					{
						finishedWithError = true;
						OnError(new ExceptionEventArgs(ex));
					}
					else
						throw;
				}

				DialogResult = DialogResult.OK;
				Close();	// in case the form isn't modal
			}
		}

		private void nextButton_Click(object sender, EventArgs e)
		{
			if (!controller.CanMoveNext(true))
				UpdateNavigation();
			else
			{
				suppressChangedUpdate = true;
				controller.MoveNext(true);
				suppressChangedUpdate = false;

				UpdateAll();
			}
		}

		private void previousButton_Click(object sender, EventArgs e)
		{
			if (!controller.CanMovePrevious(true))
				UpdateNavigation();
			else
			{
				suppressChangedUpdate = true;
				controller.MovePrevious(true);
				suppressChangedUpdate = false;

				UpdateAll();
			}
		}

		private void cancelButton_Click(object sender, EventArgs e)
		{
			if (!controller.CanCancelWizard())
				UpdateNavigation();
			else
			{
				evaluatedCanCancelWizard = true;
				DialogResult = DialogResult.Cancel;
				Close();	// in case the form isn't modal
			}
		}

		private void okButton_Click(object sender, EventArgs e)
		{
			if (!controller.CanFinishWizard())
				UpdateNavigation();
			else
			{
				controller.Finish(false);

				DialogResult = DialogResult.OK;
				Close();	// in case the form isn't modal
			}
		}

		private void navigationPanel_Paint(object sender, PaintEventArgs e)
		{
			int width = navigationPanel.Width;

			e.Graphics.DrawLine(SystemPens.ControlDark, 0, 1, width, 1);
			e.Graphics.DrawLine(SystemPens.ControlLight, 0, 2, width, 2);
		}
		#endregion
	}
}